package bubbleSort;

import shared.Buffer;
import shared.MyComparable;
import shared.MyStop;
import shared.SortInput;
import shared.SortInputVisitor;

public class BubbleSorter<T extends Comparable<? super T>> implements Runnable{
	
	private final lock.Lock isDoneLock = new lock.ConcreteLock(true);
	private boolean started;
	private boolean done;
	private BubbleSorterState<T> state;
	private final Buffer<SortInput<T>> inputBuffer;
	private final Buffer<SortInput<T>> outputBuffer;

	public void setDone(final boolean done) {
		this.done = done;
	}

	public boolean isDone() {
		return done;
	}	
	
	private BubbleSorterState<T> getState() {
		return this.state;
	}
	
	private void setState(final BubbleSorterState<T> state){
		this.state = state;
	}
	
	public Buffer<SortInput<T>> getInputBuffer() {
		return inputBuffer;
	}
		
	public Buffer<SortInput<T>> getOutputBuffer() {
		return outputBuffer;
	}

	/**
	 * Constructor of BubbleSorter.
	 * @param inputBuffer is the inputBuffer.
	 */
	public BubbleSorter(final Buffer<SortInput<T>> inputBuffer) {
		this.state = new NoSorterStarted<T>(this);
		this.inputBuffer = inputBuffer;
		this.outputBuffer = new Buffer<SortInput<T>>();
	}

	/**
	 * Starts this sorter when it is not started.
	 */
	private void startSorting(){
		if(!this.started){
			new Thread(this).start();
			this.started = true;
		}
	}

	@Override
	public void run() {
		while(!this.isDone()){
			final SortInput<T> input = this.getInputBuffer().get();
			sort(input);
		}
		this.isDoneLock.unlock();
	}

	/**
	 * Sorts the input in the outputBuffer when the input is a {@link MyComparable.}
	 * Sets this run as done when the input is a {@link MyStop}.
	 * @param input is the input.
	 */
	private void sort(final SortInput<T> input) {
		input.accept(new SortInputVisitor<T>() {

			@Override
			public void visit(final MyComparable<T> firstComparable) {
				final SortInput<T> secondinput = BubbleSorter.this.getInputBuffer().get();
				secondinput.accept(new SortInputVisitor<T>() {
					@Override
					public void visit(final MyComparable<T> secondComparable) {
						if(firstComparable.getComparable().compareTo(secondComparable.getComparable()) > 0){
						BubbleSorter.this.getOutputBuffer().put(secondComparable);
						BubbleSorter.this.getState().accept(new BubbleSorterStateVisitor<T>() {

							@Override
							public void visit(
									final SorterStarted<T> sorterStarted) {
								
							}

							@Override
							public void visit(
									final NoSorterStarted<T> noSorterStarted) {
								final BubbleSorter<T> sorter = new BubbleSorter<>(BubbleSorter.this.getOutputBuffer());
								sorter.startSorting();
								BubbleSorter.this.setState(new SorterStarted<T>(BubbleSorter.this,sorter));
							}
						});
						sort(firstComparable);
						} else {
							BubbleSorter.this.getOutputBuffer().put(firstComparable);
							sort(secondComparable);
						}
					}

					@Override
					public void visit(final MyStop<T> myStop) {	
						BubbleSorter.this.getOutputBuffer().put(firstComparable);
						BubbleSorter.this.getOutputBuffer().put(myStop);
						BubbleSorter.this.setDone(true);
					}
				
				});			
			}

			@Override
			public void visit(final MyStop<T> myStop) {
				BubbleSorter.this.getOutputBuffer().put(myStop);
				BubbleSorter.this.setDone(true);
			}
		});
	}

	/**
	 * Provides the result of this sort.
	 * Locks until the result is calculated.
	 * @return the result of this sort.
	 */
	public Buffer<T> getResult() {
		this.startSorting();
		this.isDoneLock.lock();
		return this.getState().getResult();
	}
}
